/* HueShift.cpp
Michael Zahniser

Function definitions for the HueShift class.
*/

#include "HueShift.h"

#include "_sdl/PixMap.h"
#include "_common/min_max.h"



// Default constructor.
// Initialize the key to an invalid value so that the LUT will always be
// updated for the first image.
HueShift::HueShift()
	: key(0)
{
}



// Rotate the hues in the given surface by the given angle (in degrees).
// That is, 60 degrees maps R to Y, G to C, B to M.
// Optionally flip the hues first (R <-> R, Y <-> M, G <-> B, C <-> C).
void HueShift::operator()(Surface &surface, int angle, bool flip)
{
	PixMap pix(surface);

	// Map the angle from [0, 360) to [0, HUES), rounding.
	angle = (angle * HUES + 180) / 360;
	// Bail out if the format doesn't have a separate byte for each color channel,
	// or if nothing needs to be done (angle and flip are both 0).
	if(pix.Format().BytesPerPixel != 4 || (!angle && !flip))
		return;

	// Only update the lookup table if the format has changed.
	if(IsNewByteOrder(surface))
		UpdateLUT(surface);

	for(int y = 0; y < pix.Height(); ++y)
	{
		Uint32 *it = pix.Begin(y);
		const Uint32 *end = pix.End(y);
		// We'll read the input byte by byte, but write out 32-bit blocks.
		unsigned char *cit = reinterpret_cast<unsigned char *>(it);

		for( ; it != end; ++it)
		{
			// Extract the red, green, and blue bytes.
			// The Windows byte order is BGR, so accessing them in this order speeds things
			// up a tiny bit on Windows.
			int b = cit[bi];
			int g = cit[gi];
			int r = cit[ri];
			cit += 4;

			// Figure out the minimum and maximum of the three channels.
			int minValue, maxValue;
			extremes(r, g, b, minValue, maxValue);
			int range = maxValue - minValue + 1;
			// range is in [1, 256].

			// This offset will be added to the final color value.
			Uint32 offset = minValue + (minValue << 8);
			offset += offset << 16;
			offset &= aMaskC;

			// Determine the hue.
			int hue = Hue(r, g, b, maxValue, range);
			// Map the hue from the range [0, 6 * range) to the range [0, HUES).
			hue = (HUES * hue) / (6 * range);
			// This branch is 100% predictable, so it doesn't cost much.
			if(flip)
				hue = -hue;
			hue += angle;

			// Construct the output value. The range is the saturation.
			// colorLUT's values all have a minimum channel of 0, so all three channels must have
			// this pixel's minimum value added to them.
			*it = (*it & aMask) + Color(hue, range - 1) + offset;
		}
	}
}



// Update the lookup table, if necessary, to match the given format.
void HueShift::UpdateLUT(const Surface &surface)
{
	UpdateByteOrder(surface);

	unsigned char *it = reinterpret_cast<unsigned char *>(colorLUT);
	for(int y = 0; y < HUES; ++y)
	{
		// Map the hue to a range such that there are 256 steps between each
		// of the main colors.
		int hue = (y * 6 * 256 + HUES / 2) / HUES;
		int r, g, b;

		switch(hue / 256)
		{
			case 0:
				r = 255;
				g = hue;
				b = 0;
				break;
			case 1:
				r = 256 * 2 - hue;
				g = 255;
				b = 0;
				break;
			case 2:
				r = 0;
				g = 255;
				b = hue - 256 * 2;
				break;
			case 3:
				r = 0;
				g = 4 * 256 - hue;
				b = 255;
				break;
			case 4:
				r = hue - 4 * 256;
				g = 0;
				b = 255;
				break;
			default: // case 5.
				r = 255;
				g = 0;
				b = 6 * 256 - hue;
				break;
		}


		for(int x = 0; x <= 255; ++x)
		{
			// Scale the hue based on the brightness, rounding the result.
			it[ri] = (r * x + 127) / 255;
			it[gi] = (g * x + 127) / 255;
			it[bi] = (b * x + 127) / 255;
			it[ai] = 0;
			it += 4;
		}
	}
}



// Check the byte ordering of the given format. Return true if it is different
// from the byte ordering of the lookup table.
inline bool HueShift::IsNewByteOrder(const Surface &surface)
{
	return (surface.Color(1, 2, 3) != key);
}



// Update the byte indices to match this new format.
void HueShift::UpdateByteOrder(const Surface &surface)
{
	key = surface.Color(1, 2, 3);
	unsigned char *cit = reinterpret_cast<unsigned char *>(&key);

	for(int i = 0; i < 4; ++i)
		switch(*cit++)
		{
			case 1:
				ri = i;
				break;
			case 2:
				gi = i;
				break;
			case 3:
				bi = i;
				break;
			default:
				ai = i;
				break;
		}

	// Store the mask for the alpha channel, and its complement.
	aMask = 0;
	reinterpret_cast<unsigned char *>(&aMask)[ai] = 255;
	aMaskC = ~aMask;
}



// Given (r, g, b) values and their maximum and range, return a hue in [0, 6 * range).
inline int HueShift::Hue(int r, int g, int b, int maximum, int range)
{
	// We're using a bitwise & rather than modular arithmetic to keep the hue
	// in the normal range, so it doesn't matter if hue is negative.
	if(r == maximum)
		return g - b;
	if(g == maximum)
		return b - r + 2 * range;
	// b == maximum
	return r - g + 4 * range;
}



// Map the given hue and saturaton to a color.
inline Uint32 HueShift::Color(int hue, int saturation) const
{
	return colorLUT[saturation + (hue & (HUES - 1)) * SATS];
}
